跳到主要内容

13. 条件请求 (Conditional Requests)

条件请求 (conditional request) 是带有一个或多个请求头部字段的 HTTP 请求, 这些字段指示在将请求方法应用于目标资源之前要测试的前置条件 (precondition). 第 13.2 节定义了何时评估前置条件, 以及存在多个前置条件时它们的优先级顺序.

条件 GET 请求是 HTTP 缓存更新 [CACHING] 最高效的机制. 条件也可以应用于会改变状态的方法, 例如 PUT 和 DELETE, 以防止"丢失更新 (lost update)"问题: 一个客户端意外覆盖另一个并行操作的客户端的工作.

13.1. 前置条件 (Preconditions)

前置条件通常根据目标资源整体的状态 (其当前值集合), 或者根据先前获得的表示中观察到的状态 (该集合中的一个值) 来定义. 如果一个资源有多个当前表示, 且每个表示都有自己的可观察状态, 则前置条件会假定每个请求到选定表示 (selected representation, 第 3.2 节) 的映射随时间保持一致. 无论如何, 如果映射不一致, 或服务器无法选择适当的表示, 那么当前置条件评估为 false 时不会造成危害.

下面定义的每个前置条件都由一次比较组成: 将从目标资源先前表示中获得的一组验证器 (validators), 与选定表示当前状态中的验证器进行比较 (第 8.8 节). 因此, 这些前置条件评估的是目标资源状态自客户端已知的某个给定状态以来是否发生了变化. 这种评估的效果取决于方法语义和所选条件, 如第 13.2 节所定义.

由其他规范作为扩展字段定义的其他前置条件, 可能会对所有接收者, 目标资源的一般状态, 或一组资源施加条件. 例如, WebDAV 中的 "If" 头部字段可以在接收者理解并实现该字段的情况下, 让请求以多个资源的各个方面为条件, 例如锁 ([WEBDAV], 第 10.4 节).

只有当前置条件在未知时可以安全忽略 (如 If-Modified-Since), 或者可以针对给定用例假定已经部署, 或者目标资源的某些其他属性会表明实现支持时, 前置条件的可扩展性才是可能的. 这鼓励将重点放在共同标准的相互约定部署上.

13.1.1. If-Match

"If-Match" 头部字段使请求方法以接收者源服务器满足以下条件为前置条件: 当字段值为 "*" 时, 源服务器至少拥有目标资源的一个当前表示; 或者源服务器拥有目标资源的一个当前表示, 且该表示具有与字段值中给出的实体标签列表成员相匹配的实体标签 (entity tag).

源服务器在为 If-Match 比较实体标签时, 必须使用强比较函数 (第 8.8.3.2 节), 因为客户端意图通过此前置条件防止在表示数据发生任何变化后仍应用该方法.

If-Match = "*" / #entity-tag

示例:

If-Match: "xyzzy"
If-Match: "xyzzy", "r2d2xxxx", "c3piozzzz"
If-Match: *

If-Match 最常与会改变状态的方法一起使用 (例如 POST, PUT, DELETE), 以便在多个用户代理可能并行操作同一资源时防止意外覆盖 (即防止"丢失更新"问题). 一般而言, 它可以与任何涉及选择或修改表示的方法一起使用, 以便在选定表示的当前实体标签不是 If-Match 字段值中的成员时中止请求.

当源服务器收到一个选择表示的请求, 且该请求包含 If-Match 头部字段时, 源服务器必须在执行该方法之前按照第 13.2 节评估 If-Match 条件.

要评估收到的 If-Match 头部字段:

  1. 如果字段值为 "*", 则当源服务器拥有目标资源的当前表示时, 条件为 true.

  2. 如果字段值是实体标签列表, 则当列出的任一标签与选定表示的实体标签匹配时, 条件为 true.

  3. 否则, 条件为 false.

评估 If-Match 条件的源服务器在条件评估为 false 时禁止执行所请求的方法. 相反, 源服务器可以通过响应 412 (Precondition Failed) 状态码来表明条件请求失败. 另一种方式是, 如果该请求是一个看起来已经应用于选定表示的状态变更操作, 源服务器可以响应 2xx (Successful) 状态码 (即用户代理请求的变更已经成功, 但用户代理可能并不知道, 也许是因为先前的响应丢失, 或其他用户代理做出了等效变更).

当变更请求看起来已经应用时, 允许源服务器发送成功响应对于许多创作型用例更高效, 但如果多个用户代理正在发出非常相似但并不协作的变更请求, 则会带来一些风险. 例如, 多个用户代理将公共资源作为信号量写入 (例如非原子递增) 时, 可能会发生冲突并潜在丢失重要的状态转换. 对于这类资源, 源服务器更适合严格处理, 即对于不安全方法上每个失败的前置条件都发送 412. 在其他情况下, 从成功响应中排除 ETag 字段可能会促使用户代理下一步执行 GET, 以消除对资源当前状态的困惑.

客户端可以在 GET 请求中发送 If-Match 头部字段, 以表示如果选定表示不匹配, 它更希望收到 412 (Precondition Failed) 响应. 然而, 这只在范围请求 (第 14 章) 中有用, 用于在不希望获得新表示时完成先前收到的部分表示. 当客户端更希望接收新表示时, If-Range (第 13.1.5 节) 更适合范围请求.

缓存或中间方可以忽略 If-Match, 因为它的互操作性特性只对源服务器是必要的.

注意, If-Match 头部字段如果使用列表值且其中同时包含 "" 和其他值 (包括其他 "" 实例), 在语法上是无效的 (因此不允许生成), 并且进一步说, 它不太可能具备互操作性.

13.1.2. If-None-Match

"If-None-Match" 头部字段使请求方法以接收者缓存或源服务器满足以下条件为前置条件: 当字段值为 "*" 时, 接收者没有目标资源的任何当前表示; 或者接收者拥有一个选定表示, 且该表示的实体标签不匹配字段值中列出的任何实体标签.

接收者在为 If-None-Match 比较实体标签时, 必须使用弱比较函数 (第 8.8.3.2 节), 因为即使表示数据发生过变化, 弱实体标签也可以用于缓存验证.

If-None-Match = "*" / #entity-tag

示例:

If-None-Match: "xyzzy"
If-None-Match: W/"xyzzy"
If-None-Match: "xyzzy", "r2d2xxxx", "c3piozzzz"
If-None-Match: W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"
If-None-Match: *

If-None-Match 主要用于条件 GET 请求, 以便用最少的事务开销高效更新缓存信息. 当客户端希望更新一个或多个具有实体标签的已存储响应时, 客户端在发出 GET 请求时应当生成一个包含这些实体标签列表的 If-None-Match 头部字段; 这允许接收者服务器在其中某个已存储响应与选定表示匹配时, 发送 304 (Not Modified) 响应来表明这一点.

If-None-Match 也可以与值 "*" 一起使用, 以防止不安全请求方法 (例如 PUT) 在客户端认为资源没有当前表示时, 无意修改目标资源的现有表示 (第 9.2.1 节). 这是"丢失更新"问题的一种变体, 当多个客户端尝试为目标资源创建初始表示时可能会出现.

当源服务器收到一个选择表示的请求, 且该请求包含 If-None-Match 头部字段时, 源服务器必须在执行该方法之前按照第 13.2 节评估 If-None-Match 条件.

要评估收到的 If-None-Match 头部字段:

  1. 如果字段值为 "*", 则当源服务器拥有目标资源的当前表示时, 条件为 false.

  2. 如果字段值是实体标签列表, 则当列出的某个标签与选定表示的实体标签匹配时, 条件为 false.

  3. 否则, 条件为 true.

评估 If-None-Match 条件的源服务器在条件评估为 false 时禁止执行所请求的方法; 相反, 如果请求方法是 GET 或 HEAD, 源服务器必须响应 304 (Not Modified) 状态码, 或者对于所有其他请求方法, 响应 412 (Precondition Failed) 状态码.

关于缓存如何处理收到的 If-None-Match 头部字段的要求, 定义在 [CACHING] 的第 4.3.2 节.

注意, If-None-Match 头部字段如果使用列表值且其中同时包含 "" 和其他值 (包括其他 "" 实例), 在语法上是无效的 (因此不允许生成), 并且进一步说, 它不太可能具备互操作性.

13.1.3. If-Modified-Since

"If-Modified-Since" 头部字段使 GET 或 HEAD 请求方法以选定表示的修改日期晚于字段值中给出的日期为前置条件. 如果选定表示的数据没有发生变化, 则避免传输该数据.

If-Modified-Since = HTTP-date

该字段的一个示例是:

If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT

如果请求包含 If-None-Match 头部字段, 接收者必须忽略 If-Modified-Since; If-None-Match 中的条件被认为是 If-Modified-Since 中条件的更准确替代, 二者组合只为了与可能未实现 If-None-Match 的旧中间方互操作.

如果收到的字段值不是有效的 HTTP-date, 字段值有多个成员, 或者请求方法既不是 GET 也不是 HEAD, 接收者必须忽略 If-Modified-Since 头部字段.

如果资源没有可用的修改日期, 接收者必须忽略 If-Modified-Since 头部字段.

接收者必须按照源服务器的时钟来解释 If-Modified-Since 字段值中的时间戳.

If-Modified-Since 通常用于两个不同目的: 1) 允许高效更新没有实体标签的缓存表示; 2) 将 Web 遍历的范围限制为最近发生变化的资源.

当用于缓存更新时, 缓存通常会使用被缓存消息的 Last-Modified 头部字段值来生成 If-Modified-Since 的字段值. 在时钟同步较差, 或服务器选择只接受精确时间戳匹配的情况下, 这种行为最具互操作性 (原因是当源服务器时钟被校正, 或表示从归档备份中恢复时, Last-Modified 日期可能看起来"向过去回退"). 不过, 缓存偶尔会基于其他数据生成字段值, 例如被缓存消息的 Date 头部字段, 或收到该消息时的时钟时间, 尤其是在被缓存消息不包含 Last-Modified 头部字段时.

当用于将检索范围限制在最近的时间窗口时, 用户代理会基于自己的时钟, 或基于先前响应中从服务器收到的 Date 头部字段, 生成 If-Modified-Since 字段值. 如果源服务器选择基于选定表示的 Last-Modified 头部字段进行精确时间戳匹配, 则它无法帮助用户代理将数据传输限制为仅包含指定窗口期间发生变化的资源.

当源服务器收到一个选择表示的请求, 且该请求包含 If-Modified-Since 头部字段但不包含 If-None-Match 头部字段时, 源服务器应当在执行该方法之前按照第 13.2 节评估 If-Modified-Since 条件.

要评估收到的 If-Modified-Since 头部字段:

  1. 如果选定表示的最后修改日期早于或等于字段值中给出的日期, 则条件为 false.

  2. 否则, 条件为 true.

评估 If-Modified-Since 条件的源服务器在条件评估为 false 时不应执行所请求的方法; 相反, 源服务器应当生成 304 (Not Modified) 响应, 其中只包含对识别或更新先前缓存响应有用的元数据.

关于缓存如何处理收到的 If-Modified-Since 头部字段的要求, 定义在 [CACHING] 的第 4.3.2 节.

13.1.4. If-Unmodified-Since

"If-Unmodified-Since" 头部字段使请求方法以选定表示的最后修改日期早于或等于字段值中给出的日期为前置条件. 在用户代理没有该表示实体标签的情况下, 该字段完成与 If-Match 相同的目的.

If-Unmodified-Since = HTTP-date

该字段的一个示例是:

If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT

如果请求包含 If-Match 头部字段, 接收者必须忽略 If-Unmodified-Since; If-Match 中的条件被认为是 If-Unmodified-Since 中条件的更准确替代, 二者组合只为了与可能未实现 If-Match 的旧中间方互操作.

如果收到的字段值不是有效的 HTTP-date (包括字段值看起来是日期列表的情况), 接收者必须忽略 If-Unmodified-Since 头部字段.

如果资源没有可用的修改日期, 接收者必须忽略 If-Unmodified-Since 头部字段.

接收者必须按照源服务器的时钟来解释 If-Unmodified-Since 字段值中的时间戳.

If-Unmodified-Since 最常与会改变状态的方法一起使用 (例如 POST, PUT, DELETE), 以便在多个用户代理可能并行操作某个资源且该资源的表示不提供实体标签时防止意外覆盖 (即防止"丢失更新"问题). 一般而言, 它可以与任何涉及选择或修改表示的方法一起使用, 以便在选定表示的最后修改日期自 If-Unmodified-Since 字段值中给出的日期以来发生变化时中止请求.

当源服务器收到一个选择表示的请求, 且该请求包含 If-Unmodified-Since 头部字段但不包含 If-Match 头部字段时, 源服务器必须在执行该方法之前按照第 13.2 节评估 If-Unmodified-Since 条件.

要评估收到的 If-Unmodified-Since 头部字段:

  1. 如果选定表示的最后修改日期早于或等于字段值中给出的日期, 则条件为 true.

  2. 否则, 条件为 false.

评估 If-Unmodified-Since 条件的源服务器在条件评估为 false 时禁止执行所请求的方法. 相反, 源服务器可以通过响应 412 (Precondition Failed) 状态码来表明条件请求失败. 另一种方式是, 如果该请求是一个看起来已经应用于选定表示的状态变更操作, 源服务器可以响应 2xx (Successful) 状态码 (即用户代理请求的变更已经成功, 但用户代理可能并不知道, 也许是因为先前的响应丢失, 或其他用户代理做出了等效变更).

当变更请求看起来已经应用时, 允许源服务器发送成功响应对于许多创作型用例更高效, 但如果多个用户代理正在发出非常相似但并不协作的变更请求, 则会带来一些风险. 在这些情况下, 源服务器更适合严格处理, 即对于不安全方法上每个失败的前置条件都发送 412.

客户端可以在 GET 请求中发送 If-Unmodified-Since 头部字段, 以表示如果选定表示已经被修改, 它更希望收到 412 (Precondition Failed) 响应. 然而, 这只在范围请求 (第 14 章) 中有用, 用于在不希望获得新表示时完成先前收到的部分表示. 当客户端更希望接收新表示时, If-Range (第 13.1.5 节) 更适合范围请求.

缓存或中间方可以忽略 If-Unmodified-Since, 因为它的互操作性特性只对源服务器是必要的.

13.1.5. If-Range

"If-Range" 头部字段提供一种特殊的条件请求机制, 它类似于 If-Match 和 If-Unmodified-Since 头部字段, 但会指示接收者在验证器不匹配时忽略 Range 头部字段, 从而传输新的选定表示, 而不是返回 412 (Precondition Failed) 响应.

如果客户端拥有某个表示的部分副本, 并希望获得整个表示的最新副本, 它可以在条件 GET 中使用 Range 头部字段 (使用 If-Unmodified-Since 和 If-Match 中的一个或两个). 然而, 如果由于表示已被修改而导致前置条件失败, 客户端随后必须发起第二个请求来获得完整的当前表示.

"If-Range" 头部字段允许客户端"短路"第二个请求. 非正式地说, 其含义如下: 如果表示未改变, 则发送我在 Range 中请求的部分; 否则, 发送整个表示.

If-Range = entity-tag / HTTP-date

通过检查前三个字符中是否有 DQUOTE, 可以将有效的 entity-tag 与有效的 HTTP-date 区分开.

客户端禁止在不包含 Range 头部字段的请求中生成 If-Range 头部字段. 服务器必须忽略在不包含 Range 头部字段的请求中收到的 If-Range 头部字段. 对于不支持 Range 请求的目标资源请求, 源服务器必须忽略收到的 If-Range 头部字段.

客户端禁止生成包含被标记为弱的实体标签的 If-Range 头部字段. 客户端禁止生成包含 HTTP-date 的 If-Range 头部字段, 除非客户端没有对应表示的实体标签, 且该日期是第 8.8.2.2 节所定义意义上的强验证器.

在 Range 请求中收到 If-Range 头部字段的服务器, 必须在执行该方法之前按照第 13.2 节评估该条件.

要评估包含 HTTP-date 的收到的 If-Range 头部字段:

  1. 如果给出的 HTTP-date 验证器不是第 8.8.2.2 节所定义意义上的强验证器, 则条件为 false.

  2. 如果给出的 HTTP-date 验证器与选定表示的 Last-Modified 字段值完全匹配, 则条件为 true.

  3. 否则, 条件为 false.

要评估包含 entity-tag 的收到的 If-Range 头部字段:

  1. 如果给出的 entity-tag 验证器使用强比较函数 (第 8.8.3.2 节) 与选定表示的 ETag 字段值完全匹配, 则条件为 true.

  2. 否则, 条件为 false.

If-Range 头部字段的接收者在 If-Range 条件评估为 false 时, 必须忽略 Range 头部字段. 否则, 接收者应当按请求处理 Range 头部字段.

注意, If-Range 比较是精确匹配, 包括验证器为 HTTP-date 的情况, 因此它不同于评估 If-Unmodified-Since 条件时使用的"早于或等于"比较.

13.2. 前置条件的评估 (Evaluation of Preconditions)

13.2.1. 何时评估 (When to Evaluate)

除下文排除的情况外, 接收者缓存或源服务器必须在成功完成其正常请求检查之后, 并且正好在它将处理请求内容 (如果有) 或执行与请求方法关联的动作之前, 评估收到的请求前置条件. 如果服务器在不带这些条件的同一请求上, 在处理请求内容之前本应给出的响应状态码不是 2xx (Successful) 或 412 (Precondition Failed), 则服务器必须忽略所有收到的前置条件. 换言之, 在发生重要处理之前即可检测到的重定向和失败, 优先于前置条件评估.

如果服务器不是目标资源的源服务器, 且不能充当目标资源请求的缓存, 则该服务器禁止评估本规范定义的条件请求头部字段; 如果请求被转发, 它必须转发这些字段, 因为生成它们的客户端意图让能够提供当前表示的服务器来评估这些字段. 同样, 当本规范定义的条件请求头部字段随某个不涉及选择或修改选定表示的请求方法一起收到时, 例如 CONNECT, OPTIONS 或 TRACE, 服务器必须忽略这些字段.

注意, 协议扩展可以修改评估前置条件的条件, 或者修改评估结果的后果. 例如, immutable 缓存指令 (由 [RFC8246] 定义) 会指示缓存当持有新鲜响应时放弃转发条件请求.

虽然条件请求头部字段被定义为可用于 HEAD 方法 (以便让 HEAD 的语义与 GET 的语义保持一致), 但是发送条件 HEAD 没有什么意义, 因为成功响应的大小大约与 304 (Not Modified) 响应相同, 并且比 412 (Precondition Failed) 响应更有用.

13.2.2. 前置条件的优先级 (Precedence of Preconditions)

当一个请求中存在多个条件请求头部字段时, 字段评估的顺序就变得重要. 实践中, 本文档定义的字段会以单一的逻辑顺序一致实现, 因为"丢失更新"前置条件具有比缓存验证更严格的要求, 已验证缓存比部分响应更高效, 并且实体标签被认为比日期验证器更准确.

接收者缓存或源服务器必须按以下顺序评估本规范定义的请求前置条件:

  1. 当接收者是源服务器且存在 If-Match 时, 评估 If-Match 前置条件:

    • 如果为 true, 继续到步骤 3

    • 如果为 false, 响应 412 (Precondition Failed), 除非可以确定该状态变更请求已经成功 (参见第 13.1.1 节)

  2. 当接收者是源服务器, 不存在 If-Match, 且存在 If-Unmodified-Since 时, 评估 If-Unmodified-Since 前置条件:

    • 如果为 true, 继续到步骤 3

    • 如果为 false, 响应 412 (Precondition Failed), 除非可以确定该状态变更请求已经成功 (参见第 13.1.4 节)

  3. 当存在 If-None-Match 时, 评估 If-None-Match 前置条件:

    • 如果为 true, 继续到步骤 5

    • 如果对于 GET/HEAD 为 false, 响应 304 (Not Modified)

    • 如果对于其他方法为 false, 响应 412 (Precondition Failed)

  4. 当方法是 GET 或 HEAD, 不存在 If-None-Match, 且存在 If-Modified-Since 时, 评估 If-Modified-Since 前置条件:

    • 如果为 true, 继续到步骤 5

    • 如果为 false, 响应 304 (Not Modified)

  5. 当方法是 GET 且 Range 和 If-Range 都存在时, 评估 If-Range 前置条件:

    • 如果为 true 且 Range 适用于选定表示, 响应 206 (Partial Content)

    • 否则, 忽略 Range 头部字段并响应 200 (OK)

  6. 否则,

    • 执行所请求的方法, 并根据其成功或失败作出响应.

任何定义额外条件请求头部字段的 HTTP 扩展, 都应当定义这些字段相对于本文档所定义字段以及实践中可能出现的其他条件的评估顺序.